package mat;


import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.log4j.Logger;

public class BinaryMatrixReader
{
	private Logger log;
	private String name;
	private String filePath;
	private int rows;
	private int cols;
	private Mat matrix;

	private long retryIfExistsInSec = 30;
	private long maxRetryInSec = 3600;
	
	public BinaryMatrixReader(String name, String filePath, int rows, int cols)
	{
		this.log = Logger.getLogger(this.getClass());
		this.name = name;
		this.filePath = filePath;
		this.rows = rows;
		this.cols = cols;
	}
	

	public void process() throws IOException, InterruptedException
	{
		long t0 = System.currentTimeMillis();
		
		MultiProtocolFile mpf;
		
		long t = System.currentTimeMillis() + maxRetryInSec * 1000;
		while (t > System.currentTimeMillis())
		{
			if (canReadAny())
				break;
			
			log.info("Wait for " + filePath + "...");
			Thread.sleep(retryIfExistsInSec * 1000);
		}
		
		mpf = new MultiProtocolFile(filePath);
		if (mpf.exists())
		{
			processFlatFile(mpf);
		}
		else
		{
			String zipFileName;
			int idx = filePath.lastIndexOf('.');
			
			if (idx < 0)
				zipFileName = filePath;
			else
				zipFileName = filePath.substring(0,idx) + ".zip";

			mpf = new MultiProtocolFile(zipFileName);
			
			processZipFile(mpf);
		}
		
		long t1 = System.currentTimeMillis();
		log.info("Processed " + mpf.getName() + ", " + (t1 -t0) + "ms");
	}
	
	// FIXME, see FileParser
	private boolean canReadAny() throws IOException
	{
		MultiProtocolFile mpf = new MultiProtocolFile(filePath);
		if (mpf.exists())
			return true;
		
		String zipFileName;
		int idx = filePath.lastIndexOf('.');
		
		if (idx < 0)
			zipFileName = filePath;
		else
			zipFileName = filePath.substring(0,idx) + ".zip";

		mpf = new MultiProtocolFile(zipFileName);
		return 	mpf.exists();		

	}
	
	
	private void processZipFile(MultiProtocolFile mpf) throws IOException
	{
		log.info("Processing " + mpf.getName());
		
		String name = new File(filePath).getName();
		
		ZipInputStream istr = new ZipInputStream(mpf.getInputStream());
		ZipEntry ze = null;
		while ( (ze = istr.getNextEntry()) != null )
		{					
			String zeName = ze.getName();
			if (zeName.lastIndexOf('/') > 1)
				zeName = zeName.substring(zeName.lastIndexOf('/') + 1);
			
			// load subFileName instead of fnOrig
			if (zeName.equalsIgnoreCase(name))
				break;
			
			ze = null;
		}
		
		
		if (ze == null)
		{
			throw new IOException("Cannot find '" + filePath + "' in " + mpf);
		}
		
		log.info("Start Reading from: " + ze.getName() + "@" + mpf);
		try
		{
			readFully(istr);
		}
		finally
		{
			if (istr != null)
				istr.close();
		}

	}
	
	private void processFlatFile(MultiProtocolFile mpf) throws IOException
	{
		log.info("Processing " + mpf.getName());
		
		InputStream is = mpf.getInputStream();
		try
		{
			readFully(is);
		}
		finally
		{
			if (is != null)
				is.close();
		}
	}
	
	
	private void readFully(InputStream is) throws IOException
	{
		DataInputStream rdr = new DataInputStream(new BufferedInputStream(is));

		int chunkSize = 10000;
		LinkedList<double[]> chunks = new LinkedList<double[]>();
		
		int cntDoubles = 0;
		try
		{
			int i = 0;
			double[] chunk = null;
			for (;;) 
			{
				double d = rdr.readDouble();
				if (cntDoubles == 0 || i == chunkSize)
				{
					chunk = new double[chunkSize];
					chunks.add(chunk);
					i = 0;
				}
				chunk[i++] = d;
				cntDoubles++;
			}
		}
		catch (EOFException e)
		{
		}
		
		if (cntDoubles == 0)
		{
			rows = 0;
			cols = 0;
			matrix = new Mat(name, new double[0][0]);
			return;
		}
		
		if (rows == 0)
			rows = cntDoubles / cols;
		if (cntDoubles == 0 || cntDoubles < cols) 
		{
			// DIVIDE BY ZERO CONDITION
			log.error("Corrupt Input File - Check File [" + filePath + "/" + name + "]");
			throw new IOException();
		} 
		else 
		{
			cols = cntDoubles/rows;
		}
		
		Iterator<double[]> it = chunks.iterator();
		double[] chunk = it.next();
		int k = 0;
		double[][] data = new double[rows][cols];
		for (int j = 0; j < cols; j++)
		{
			for (int i = 0; i < rows; i++)
			{
				if (k == chunkSize)
				{
					it.remove();
					chunk = it.next();
					k = 0;
				}
				data[i][j] = chunk[k++];
			}
		}
				
		matrix = new Mat(name, data);
	}


	public Mat getMatrix()
	{
		return matrix;
	}
	
	

}
